// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;

namespace SnapObjects.Data
{
    internal abstract class SqlSelectStatementParser : SqlQueryStatementParser
    {
        public SqlSelectStatementParser(
            ISqlStatementFactory factory, ISqlGenerationHelper helper) : base(factory, helper)
        {

        }

        public override SqlStatement ParseStatement(ISqlTokenizer tokenizer, bool isClause)
        {
            var statement = new SqlSelectStatement();

            _isClause = isClause;

            do
            {
                if (tokenizer.Current is SqlCommentToken ||
                    tokenizer.Current is SqlWhitespaceToken)
                {
                    continue;
                }

                if (tokenizer.Current is SqlKeywordToken token)
                {
                    if (token.Keyword == SqlKeywords.SELECT && tokenizer.MoveNext())
                    {
                        this.ParseSelect(tokenizer, statement);

                        this.ParseFrom(tokenizer, statement);

                        this.ParseWhere(tokenizer, statement);

                        this.ParseGroupBy(tokenizer, statement);

                        this.ParseHaving(tokenizer, statement);

                        this.ParseOrderBy(tokenizer, statement);

                        this.ParseUnion(tokenizer, statement);

                        break;
                    }
                    else
                    {
                        throw new InvalidCastException(tokenizer.Current.Text);
                    }
                }
                else
                {
                    throw new InvalidCastException(tokenizer.Current.Text);
                }
            } while (tokenizer.MoveNext());

            return statement;
        }

        protected virtual bool ParseSelect(ISqlTokenizer tokenizer, SqlSelectStatement statement)
        {
            var result = false;

            do
            {
                if (tokenizer.Current is SqlKeywordToken key)
                {
                    if (key.Keyword == SqlKeywords.SELECT)
                    {
                        var subStmt = this.ParseClause(tokenizer, statement.Select);

                        statement.Select.Add(subStmt);

                        if (tokenizer.Current == null)
                        {
                            break;
                        }
                    }
                    else if (key.Keyword == SqlKeywords.FROM
                        || key.Keyword == SqlKeywords.WHERE
                        || key.Keyword == SqlKeywords.HAVING
                        || key.Keyword == SqlKeywords.ORDER
                        || key.Keyword == SqlKeywords.UNION)
                    {
                        result = true;

                        break;
                    }
                }

                if (this.IsStatementBracketEnd(tokenizer))
                {
                    break;
                }
                else
                {
                    statement.Select.Add(tokenizer.Current);
                }

                if (tokenizer.Current is SqlCharacterToken character
                    && character.Character == SqlCharacters.Comma)
                {
                    statement.Select.Add(SqlWhitespaceToken.Whitespace);
                }

            } while (tokenizer.MoveNext());

            return result;
        }

        protected virtual bool ParseGroupBy(ISqlTokenizer tokenizer, SqlSelectStatement statement)
        {
            var result = false;

            if (tokenizer.Current.IsKeyword(SqlKeywords.GROUP))
            {
                if (tokenizer.MoveNext()
                    && tokenizer.MoveNext()
                    && tokenizer.Current.IsKeyword(SqlKeywords.BY))
                {
                    while (tokenizer.MoveNext())
                    {
                        if (tokenizer.Current is SqlKeywordToken key)
                        {
                            if (key.Keyword == SqlKeywords.SELECT)
                            {
                                var subStmt = this.ParseClause(tokenizer, statement.GroupBy);

                                statement.GroupBy.Add(subStmt);

                                if (tokenizer.Current == null)
                                {
                                    break;
                                }
                            }
                            else if (key.Keyword == SqlKeywords.HAVING
                                || key.Keyword == SqlKeywords.ORDER
                                || key.Keyword == SqlKeywords.UNION)
                            {
                                result = true;

                                break;
                            }
                        }

                        if (this.IsStatementBracketEnd(tokenizer))
                        {
                            break;
                        }
                        else
                        {
                            statement.GroupBy.Add(tokenizer.Current);
                        }

                        if (tokenizer.Current is SqlCharacterToken character
                                && character.Character == SqlCharacters.Comma)
                        {
                            statement.GroupBy.Add(SqlWhitespaceToken.Whitespace);
                        }
                    }
                }
            }

            return result;
        }

        protected virtual bool ParseHaving(ISqlTokenizer tokenizer, SqlSelectStatement statement)
        {
            var result = false;

            if (tokenizer.Current.IsKeyword(SqlKeywords.HAVING))
            {
                while (tokenizer.MoveNext())
                {
                    if (tokenizer.Current is SqlKeywordToken key)
                    {
                        if (key.Keyword == SqlKeywords.SELECT)
                        {
                            var subStmt = this.ParseClause(tokenizer, statement.Having);

                            statement.Having.Add(subStmt);

                            if (tokenizer.Current == null)
                            {
                                break;
                            }
                        }
                        else if (key.Keyword == SqlKeywords.ORDER || key.Keyword == SqlKeywords.UNION)
                        {
                            result = true;

                            break;
                        }
                    }

                    if (this.IsStatementBracketEnd(tokenizer))
                    {
                        break;
                    }
                    else
                    {
                        statement.Having.Add(tokenizer.Current);
                    }
                }
            }

            return result;
        }

        protected virtual bool ParseOrderBy(ISqlTokenizer tokenizer, SqlSelectStatement statement)
        {
            var result = false;

            if (tokenizer.Current.IsKeyword(SqlKeywords.ORDER))
            {
                if (tokenizer.MoveNext()
                    && tokenizer.MoveNext()
                    && tokenizer.Current.IsKeyword(SqlKeywords.BY))
                {
                    while (tokenizer.MoveNext())
                    {
                        if (tokenizer.Current is SqlKeywordToken key)
                        {
                            if (key.Keyword == SqlKeywords.SELECT)
                            {
                                var subStmt = this.ParseClause(tokenizer, statement.OrderBy);

                                statement.OrderBy.Add(subStmt);

                                if (tokenizer.Current == null)
                                {
                                    break;
                                }
                            }
                            else if (key.Keyword == SqlKeywords.UNION)
                            {
                                result = true;

                                break;
                            }
                        }

                        if (this.IsStatementBracketEnd(tokenizer))
                        {
                            break;
                        }
                        else
                        {
                            statement.OrderBy.Add(tokenizer.Current);
                        }

                        if (tokenizer.Current is SqlCharacterToken character
                                && character.Character == SqlCharacters.Comma)
                        {
                            statement.OrderBy.Add(SqlWhitespaceToken.Whitespace);
                        }
                    }
                }
            }

            return result;
        }

        protected virtual bool ParseUnion(ISqlTokenizer tokenizer, SqlSelectStatement statement)
        {
            bool result = false, isUnionAll = false;

            if (tokenizer.Current.IsKeyword(SqlKeywords.UNION))
            {
                while (tokenizer.MoveNext())
                {
                    if (tokenizer.Current is SqlKeywordToken key && key.IsKeyword(SqlKeywords.SELECT))
                    {
                        var union = this.ParseClause(tokenizer, null);

                        if (isUnionAll)
                        {
                            statement.Unions.Add((SqlSelectStatement)union, SqlUnionType.UnionAll);
                        }
                        else
                        {
                            statement.Unions.Add((SqlSelectStatement)union, SqlUnionType.Union);
                        }

                        //Not best practices, wait for optimization
                        statement.OrderBy.RemoveAll(token => true);
                        statement.OrderBy.AddRange(((SqlSelectStatement)union).OrderBy);
                        ((SqlSelectStatement)union).OrderBy.RemoveAll(token => true);
                        //end

                        result = true;

                        isUnionAll = false;
                    }
                    else if (tokenizer.Current.Type != SqlTokenType.Whitespace)
                    {
                        if (tokenizer.Current is SqlKeywordToken k
                            && k.Keyword == SqlKeywords.ALL)
                        {
                            isUnionAll = true;
                        }
                        else if (tokenizer.Current is SqlCharacterToken c
                            && !c.IsCharacter(SqlCharacters.OpenParentheses)
                            && !c.IsCharacter(SqlCharacters.CloseParentheses)
                            && !c.IsCharacter(SqlCharacters.Semicolon))
                        {
                            throw new InvalidCastException(tokenizer.Current.Text);
                        }
                    }

                    if (this.IsStatementBracketEnd(tokenizer))
                    {
                        break;
                    }
                }
            }

            return result;
        }
    }
}
